# @time     :2022/2/21
# @Author   :Simple
# @FileName :element
from typing import List

from appium.webdriver.common.mobileby import MobileBy
from appium.webdriver.webdriver import WebDriver
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.wait import WebDriverWait

from common.exceptions import FindElementTypesError, PageElementError, PageSelectException
from util.handle_black_list import handle_black
from util.logger import log as logger

LOCATOR_LIST = {
    'link_text': By.LINK_TEXT,
    "partial_link_text": By.PARTIAL_LINK_TEXT,
    'tag': By.TAG_NAME,
    'ios_predicate': MobileBy.IOS_PREDICATE,
    'ios_class_chain': MobileBy.IOS_CLASS_CHAIN,
    'android_viewtag': MobileBy.ANDROID_VIEWTAG,
    'android_datamatcher': MobileBy.ANDROID_DATA_MATCHER,
    'custom': MobileBy.CUSTOM,
    'image': MobileBy.IMAGE,
}


class GetElement(object):
    def __init__(self, id_=None, xpath=None, class_name=None, css=None, ios_uiautomator=None,
                 android_uiautomator=None, accessibility_id=None, context=None, timeout=5, wait=False, describe="",
                 visible=True, **kwargs):
        """
        查找页面元素，为元素指定定位器
        :param id_:     'str'
        :param xpath:       'str'
        :param class_name:      'str'
        :param css:      'str'
        :param ios_uiautomator:      'str'
        :param android_uiautomator:      'str'
        :param accessibility_id:      'str'
        :param context:      'bool'
        :param timeout:      'int'
        :param wait:      'bool'
        :param describe:      'str'
        :param visible:      'bool'
        :param kwargs:
        """
        self.timeout = timeout
        self.wait = wait
        self.describe = describe
        self.visible = visible
        self.has_context = bool(context)
        if id_:
            self.locator = (By.ID, id_)
        elif xpath:
            self.locator = (By.XPATH, xpath)
        elif class_name:
            self.locator = (By.CLASS_NAME, class_name)
        elif css:
            self.locator = (By.CSS_SELECTOR, css)
        elif ios_uiautomator:
            self.locator = (MobileBy.IOS_UIAUTOMATION, ios_uiautomator)
        elif android_uiautomator:
            if not android_uiautomator.startswith('new'):
                android_uiautomator = "new UiSelector()." + android_uiautomator
            self.locator = (MobileBy.ANDROID_UIAUTOMATOR, android_uiautomator)
        elif accessibility_id:
            self.locator = (MobileBy.ACCESSIBILITY_ID, accessibility_id)
        else:
            logger.warning(f"不支持当前类型查找元素。")
            raise FindElementTypesError("Element positioning of type is not supported.")

        if kwargs:
            if len(kwargs) > 1:
                raise ValueError("Please specify only one locator.")
            self.k, self.v = next(iter(kwargs.items()))
            try:
                self.locator = (LOCATOR_LIST[self.k], self.v)
            except KeyError:
                logger.warning(f"不支持当前类型:{self.k}查找元素")
                raise FindElementTypesError(f"Element positioning of type {self.k} is not supported.")

    @handle_black
    def get_element(self, driver: WebDriver):
        logger.debug(f"查找元素:{self.describe}")
        if self.wait and self.visible:
            element = WebDriverWait(driver, self.timeout).until(
                expected_conditions.visibility_of_element_located(self.locator))
        elif self.wait:
            element = WebDriverWait(driver, self.timeout).until(lambda ele: driver.find_element(*self.locator))
        else:
            element = driver.find_element(*self.locator)
        return element

    def find(self, driver: WebDriver):
        element = self.get_element(driver)
        if element is not None:
            logger.debug(f"元素已找到:{self.describe}→{self.locator}")
            return element
        return None

    def __get__(self, instance, owner, driver=None):
        if not instance:
            return None
        if not driver and self.has_context:
            return lambda ctx: self.__get__(instance, owner, driver=ctx)
        if not driver:
            driver = instance.driver
        return self.find(driver)

    def __set__(self, instance, value):
        if self.has_context:
            raise PageElementError("Sorry, the set descriptor doesn't support elements with context.")
        elem = self.__get__(instance, instance.__class__)
        if not elem:
            raise PageElementError("Can't set value, element not found")
        elem.send_keys(value)


def scroll_find(driver: WebDriver, text):
    """
    按文本内容滚动查找元素
    :param driver:
    :param text:
    :return:
    """
    value = f'new UiScrollable(new UiSelector().scrollable(true).instance(0))' \
            f'.scrollIntoView(new UiSelector().text("{text}").instance(0));'
    element = GetElement(android_uiautomator=value)
    return element.find(driver)


class GetElements(GetElement):
    def find(self, driver: WebDriver) -> List[WebElement]:
        """
        查找多个元素
        :param driver:
        :return:
        """
        self.visible = False
        if self.get_element(driver) is not None:
            try:
                logger.debug(f"元素已找到：{self.describe}→{self.locator}")
                return driver.find_elements(*self.locator)
            except Exception:
                return []
        else:
            return []

    def __set__(self, instance, value):
        if self.has_context:
            raise PageElementError("Sorry, the set descriptor doesn't support elements with context.")
        elems = self.__get__(instance, instance.__class__)
        if not elems:
            raise PageElementError("Can't set value, element not found")
        [elem.send_keys(value) for elem in elems]


def find_child(element: WebElement, locator) -> WebElement:
    """
    获取子元素
    :param element: 父元素
    :param locator: 定位器
    例如：(By.XPATH,"//*[@text='取消']")
    :return:
    """
    return element.find_element(*locator)


def find_children(element: WebElement, locator) -> List[WebElement]:
    """
    获取多个子元素
    :param element: 父元素
    :param locator: 定位器
    例如：(By.XPATH,"//*[@text='取消']")
    :return:
    """
    return element.find_elements(*locator)


class PageSelect(object):
    """
    选中下拉框
    """
    def __init__(self, select_elem, value=None, text=None, index=None):
        if value is not None:
            Select(select_elem).select_by_value(value)
        elif text is not None:
            Select(select_elem).select_by_visible_text(text)
        elif index is not None:
            Select(select_elem).select_by_index(index)
        else:
            raise PageSelectException('"value" or "text" or "index" options can not be all empty.')
